/**        
 * Copyright (c) 2013 by 苏州科大国创信息技术有限公司.    
 */    
package cn.tanghom.support.web;

import java.beans.PropertyEditorSupport;
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.web.PageableDefault;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.unbiz.common.DateUtil;
import com.unbiz.common.StringEscapeUtil;
import com.unbiz.common.StringUtil;

import cn.tanghom.conf.CommonKey;
import cn.tanghom.support.db.BaseActiveRecordDao;
import cn.tanghom.support.hook.AbstractModule;
import cn.tanghom.support.hook.ModuleManager;

/**
 * Base CRUD controller, M is model
 * @author bsli@ustcinfo.com
 */
abstract public class BaseController<M> implements CommonKey {
	protected static final Logger logger = LoggerFactory.getLogger(BaseController.class);

	// 页面路径 相对于WEB-INF/views,实际上就是theme的路径
	public static String THEME_PATH = "default";
		
	public static boolean DEBUG = false; //debug模式	
	
	//常用的中国日期格式
	private static String[] datePatterns = { "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
        "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss",
        "yyyy.MM.dd HH:mm", "yyyy.MM" };
	
	protected Class<M> modelClass = null;	
	
	@Autowired
	protected JpaRepository<M, Long> crudRepository;
	
	@Autowired
	protected BaseActiveRecordDao dao;
	
	/**
	 * 返回当前页面的设备
	 * @return
	 */
	public static Device getDevice(){
		HttpServletRequest req = ContextHolder.getRequest();
		String device = req.getParameter("device");
		Device d = null;
		if(device==null){
			d = Device.detect(req);
		}
		else{
			d = Device.valueOf(device);					
		}		
		if(d != null ){					
			return d;		
		}	
		return Device.pc;
	}
	
	public static void include(String js,String path){
		HttpServletRequest request = ContextHolder.getRequest();
		Map<String,String> includes = (Map<String,String>)request.getAttribute("includes");		
		includes.put(js,path);
	}
	
	public String getPara(String name){
		String value = (String)ContextHolder.getRequest().getParameter(name);
		return value;
	}
	
	public String getPara(String name,String def){
		String value = (String)ContextHolder.getRequest().getParameter(name);
		return value==null ? def: value;
	}
	
	public <T> T getAttr(String name){
		T value = (T)ContextHolder.getRequest().getAttribute(name);
		return value;
	}
	
	public BaseController<M> setAttr(String name,Object value){
		ContextHolder.getRequest().setAttribute(name, value);
		return this;
	}

	/**
	 * Redirect to url，如果有redirectionUrl参数，则跳转到redirectionUrl
	 */
	public String redirectUrl(String url) {
		HttpSession session = ContextHolder.getSession();
		String redirectionUrl = (String)session.getAttribute(BaseController.redirectUrl);
		if (StringUtils.isEmpty(redirectionUrl)) {
			redirectionUrl = getPara(BaseController.redirectUrl);
		} else {
			session.removeAttribute(BaseController.redirectUrl);
		}
		if (StringUtils.isEmpty(redirectionUrl)) {
			return "redirect:"+url;
		} else {
			return "redirect:"+redirectionUrl;
		}
	}	

	@InitBinder
	public void initBinder(WebDataBinder binder) {
		// String类型转换，将所有传递进来的String进行HTML编码，防止XSS攻击
		binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				setValue(text == null ? null : StringEscapeUtil.escapeHtml(text.trim()));
			}
			@Override
			public String getAsText() {
				Object value = getValue();
				return value != null ? value.toString() : "";
			}
		});
		
		// Date 类型转换
		binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				try {
					setValue(DateUtils.parseDate(text,datePatterns));
				} catch (ParseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
		
		// Timestamp 类型转换
		binder.registerCustomEditor(Timestamp.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				Date date;
				try {
					date = DateUtils.parseDate(text,datePatterns);
					setValue(date==null?null:new Timestamp(date.getTime()));
				} catch (ParseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();					
				}				
			}
		});
	}

	public BaseController() {
		// 把class的变量保存起来，不用每次去取
		this.setModelClass(getParamClazz());
	}	
	
	/**
	 * 获取M的class
	 *
	 * @return M
	 */
	@SuppressWarnings("unchecked")
	private Class<M> getParamClazz() {
		Class<?> cls = this.getClass();
		Type t = cls.getGenericSuperclass();
		while(!(t instanceof ParameterizedType)){
			cls = cls.getSuperclass();
			t = cls.getGenericSuperclass();
		}
		Type[] params = ((ParameterizedType) t).getActualTypeArguments();
		return (Class<M>) params[0];
	}

	public Class<M> getModelClass() {
		return modelClass;
	}

	protected void setModelClass(Class<M> modelClass) {
		this.modelClass = modelClass;
	}

	/**
	 * 获取当前登录管理员,若未登录则返回null.
	 *
	 * @return 当前登录管理员ID
	 */
	public String getLoginUserId() {
		String username = (String) SecurityUtils.getSubject().getPrincipal();
		return username;
	}

	// 输出JSON成功消息，返回null
	public String ajaxJsonSuccessMessage(String message) {
		setAttr(STATUS, SUCCESS);
		setAttr(MESSAGE, message);
		return renderJson(STATUS, SUCCESS, MESSAGE, message);
	}

	// 输出JSON错误消息，返回null
	public String ajaxJsonErrorMessage(String message,int code) {
		setAttr(STATUS, ERROR);
		setAttr(errorMessage, message);
		setAttr(statusCode, code);
		setAttr("applicationConfig", CommonKey.application.getProperties());
		return renderJson(STATUS, ERROR, errorMessage, message, statusCode, code);
	}

	// 输出JSON警告消息，返回null
	public String ajaxJsonWarnMessage(String message) {
		setAttr(STATUS, WARN);
		setAttr(MESSAGE, message);
		return renderJson(STATUS, WARN, MESSAGE, message);
	}

	

	/**
	 * todo@byron 在session里面保存errorMessages
	 * 
	 * @param error
	 */
	public void addActionError(String error) {
		Object oldMsg = getAttr("errorMessages");
		if(oldMsg!=null && oldMsg instanceof JSONArray){
			JSONArray array =(JSONArray)oldMsg;
			if (!StringUtils.isEmpty(error)) {
				array.add(error);
			}
		}
		else{		
			JSONArray array= new JSONArray();			
			array.add(error);			
			setAttr("errorMessages", array);
		}				
		
	}

	// 检查名是否唯一，ajax验证
	public void checkNameUnique() {
		String tableName = getPara("table", "");
		String value = getPara("name", "");
		String id = getPara("id");
		String propertyName = getPara("field", "name");
		if (id != null) {
			if (isExist(tableName, propertyName, value, id)) {
				renderJson("result","false");
			} else {
				renderJson("result","true");
			}
		} else {
			if (!isUnique(tableName, propertyName, value)) {
				renderJson("result","false");
			} else {
				renderJson("result","true");
			}
		}
	}
	
	/**
	 * 准备显示 model
	 * 
	 * @param id
	 * @return
	 */
	@RequestMapping(value = "/view/{id}", method = RequestMethod.GET)    
	public String viewModel(@PathVariable Long id) {
		M model = crudRepository.findOne(id);
		List<AbstractModule<M>> plugins = ModuleManager.getModules(this.modelClass);
		for (AbstractModule<M> plugin : plugins) {
			try {
				if (plugin.hook("view", model, this)) {
					plugin.viewModel(model, this);
				}
			} catch (Exception e) {
				plugin.handleError("view", e);
			}
		}
		return THEME_PATH+"/"+this.modelClass.getSimpleName().toLowerCase()+"/view";
	}

	
	public boolean validateModel(M model, String op) {
		boolean isValid = true;
		List<AbstractModule<M>> plugins = ModuleManager.getModules(this.modelClass);
		for (AbstractModule<M> plugin : plugins) {
			isValid = plugin.validateModel(model, this, op);
			if (!isValid) { // 非法的
				//addActionError("验证失败!");
				return isValid;
			}
		}
		return isValid;
	}

	/**
	 * 删除 model.delete()
	 * 
	 * @param id
	 * @return
	 */
	@RequestMapping(value = "/deleteById", produces = "application/json;charset=UTF-8", method = {RequestMethod.POST,RequestMethod.DELETE})
    @ResponseBody
	public String deleteModel(Long id) {
		M entity = crudRepository.findOne(id);
		List<AbstractModule<M>> plugins = ModuleManager.getModules(this.modelClass);
		for (AbstractModule<M> plugin : plugins) {
			try {
				if (plugin.hook("delete", entity, this)) {
					plugin.deleteModel(entity, this);
				}
			} catch (Exception e) {
				plugin.handleError("delete", e);
			}
		}
		crudRepository.delete(entity);
		return ajaxJsonSuccessMessage("成功删除！");
	}

	/**
	 * 更新 model.update()
	 * 
	 * @param id
	 * @return
	 */
	@RequestMapping(value = "/update", produces = "application/json;charset=UTF-8", method = {RequestMethod.POST,RequestMethod.PUT})
    @ResponseBody
	public String updateModel(M model) {		
		crudRepository.save(model);
		List<AbstractModule<M>> plugins = ModuleManager.getModules(this.modelClass);
		for (AbstractModule<M> plugin : plugins) {
			try {
				if (plugin.hook("update", model, this)) {
					plugin.updateModel(model, this);
				}
			} catch (Exception e) {
				plugin.handleError("update", e);
			}
		}		
		return ajaxJsonSuccessMessage("成功更新！");
	}

	/**
	 * 创建 model.create()
	 * 
	 * @param id
	 * @return
	 */
	@RequestMapping(value = "/create", produces = "application/json;charset=UTF-8", method = RequestMethod.POST)
    @ResponseBody
	public String saveModel(M model) {
		crudRepository.save(model);		
		List<AbstractModule<M>> plugins = ModuleManager.getModules(this.modelClass);
		for (AbstractModule<M> plugin : plugins) {
			try {
				if (plugin.hook("save", model, this)) {
					plugin.saveModel(model, this);
				}
			} catch (Exception e) {
				plugin.handleError("save", e);
			}
		}		
		return ajaxJsonSuccessMessage("成功创建！");
	}

	
	/**
	 * 显示列表
	 * 
	 * @param id
	 * @return
	 */
	@RequestMapping(value = "/lists", method = RequestMethod.GET)  
	public String listModel(@PageableDefault(value = 20, sort = { "id" }, direction = Direction.DESC) Pageable pageable) {
		Page<M> list = crudRepository.findAll(pageable);
		List<M> result = list.getContent();
		List<AbstractModule<M>> plugins = ModuleManager.getModules(this.modelClass);
		for (AbstractModule<M> plugin : plugins) {
			try {
				if (plugin.hook("list", result, this)) {
					plugin.listModel(result, this);
				}
			} catch (Exception e) {
				plugin.handleError("list", e);
			}
		}		
		return THEME_PATH+"/"+this.modelClass.getSimpleName().toLowerCase()+"/list";
	}
	
	/**
	 * 取Request中的数据对象
	 * @param valueType
	 * @return
	 * @throws Exception 
	 */
	protected <T> T getRequestObject(Class<T> valueType) throws IOException {
	  StringBuilder json = new StringBuilder();
	  BufferedReader reader = ContextHolder.getRequest().getReader();
	  String line = null;
	  while((line = reader.readLine()) != null){
	    json.append(line);
	  }
	  reader.close();	 
	  return JSONObject.parseObject(json.toString(), valueType);
	}	
	
	/**
	 * 取Request中的数据对象
	 * @param valueType
	 * @return
	 * @throws Exception 
	 */
	protected JSONObject getRequestJSONObject() throws IOException {
	  StringBuilder json = new StringBuilder();
	  BufferedReader reader = ContextHolder.getRequest().getReader();
	  String line = null;
	  while((line = reader.readLine()) != null){
	    json.append(line);
	  }
	  reader.close();
	  return JSONObject.parseObject(json.toString());
	}
	
	@ExceptionHandler()
	public void handleException(Exception exception, HttpServletRequest request, HttpServletResponse response) {
		logger.error(exception.getMessage(), exception);
		HttpSession httpSession = request.getSession();
		
		if(exception instanceof CannotGetJdbcConnectionException) {
			httpSession.setAttribute("message", "不能获取数据库连接，请联系管理员！");
			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
		}
		RequestDispatcher rd =  request.getSession().getServletContext().getRequestDispatcher("/error"); 
        try {
			rd.forward(request, response);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}
	
	/**
	 * Render with json
	 */
	public static String renderJson(Object ... kv) {
		if(kv.length==1){
			return JSON.toJSONString(kv[0]);
		}
		JSONObject json = new JSONObject();
		for(int i = 0;i<kv.length-1;i+=2){
			json.put(kv[i].toString(), kv[i+1]);
		}
		return json.toJSONString();
	}
	
	/**
	 * 根据表名、属性名称、属性值判断在数据库中是否唯一(若新修改的值与原来值相等则直接返回true).
	 *
	 * @param tableName
	 *          表名
	 * @param propertyName
	 *          属性名称
	 * @param value
	 *          属性值
	 * @return boolean
	 */
	public boolean isUnique(String tableName, String propertyName, String value) {
		if (StringUtil.isNotBlank(tableName) && StringUtil.isNotBlank(propertyName) && StringUtil.isNotBlank(value)) {
			String sql = "select count(*) from " + tableName + " where " + propertyName + " = '"+value+"' limit 0,1";
			return dao.count(sql,null) == 0;
		} else {
			return false;
		}
	}

	public boolean isExist(String tableName, String propertyName, String value, String id) {
		if (StringUtil.isNotBlank(tableName) && StringUtil.isNotBlank(propertyName) && StringUtil.isNotBlank(value)) {
			Map<String,Object> params = new HashMap<>();
	    	params.put("id", id);
	    	params.put("propertyName", value);
	    	String sql = "select count(*) from " + tableName + " where " + propertyName + " = :propertyName AND id != :id limit 0,1 ";
			return dao.count(sql,params) >0;
		} else {
			return false;
		}		
	}
}
